package com.danguo.happymonkey;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import com.danguo.happymonkey.R;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;

public class GalacticPlane
{
   
   static final float size = 10f; 
// number of coordinates per vertex in this array
   static final int COORDS_PER_VERTEX = 3;
   static float theCoords[] = { -size,  0f, size,  // top left
                                -size,  0f, -size,   // bottom left
                                 size,  0f, -size,  // bottom right
                                 size,  0f, size}; // top right

   static float theUVCoords[] = { 1f, 0f,
                                  0f, 0f,
                                  0f, 1f,
                                  1f, 1f };
   
   private final short drawOrder[] = { 0, 1, 2, 2, 3, 0 }; // order to draw vertices

   private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
//   private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
   private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
   private static final int UV_STRIDE = 2 * 4;
   
   private final String theVertexShader =
         // This matrix member variable provides a hook to manipulate
         // the coordinates of the objects that use this vertex shader
         "uniform mat4 uMVPMatrix;" +
         "attribute vec4 vPosition;" +
         "attribute vec2 a_texCoords;" +
         "varying vec2 v_texCoords;" +
         "void main() {" +
         // the matrix must be included as a modifier of gl_Position
            "gl_Position = uMVPMatrix*vPosition;" +
            "v_texCoords = a_texCoords;" +
         "}";

     private final String theFragmentShader =
         "precision mediump float;" +
         //"uniform vec4 vColor;" +
         "varying vec2 v_texCoords;\n" +
         "uniform sampler2D sTexture;\n" +
         "void main() {" +
         //"  gl_FragColor = vColor;" +
         "  gl_FragColor = texture2D(sTexture, v_texCoords);\n" +
         "}";
   
   private int theProgramHandle;
//   private int theColorHandle;
   private int thePositionHandle;
   private int theTextureHandle;
   
   private int[] theTextures;

   // Set color with red, green, blue and alpha (opacity) values
//   private final float color[] = { 0.2f, 0.709803922f, 0.1f, 1.0f };
   private ShortBuffer theDrawListBuffer;
   private FloatBuffer theVertexBuffer;
   private FloatBuffer theUVBuffer;
   private int theMVPMatrixHandle;
   
   GalacticPlane( GLSurfaceView aContext )
   {
   // initialize vertex byte buffer for shape coordinates
      ByteBuffer bb = ByteBuffer.allocateDirect(
      // (# of coordinate values * 4 bytes per float)
              theCoords.length * 4);
      bb.order(ByteOrder.nativeOrder());
      theVertexBuffer = bb.asFloatBuffer();
      theVertexBuffer.put(theCoords);
      theVertexBuffer.position(0);

      // initialize byte buffer for the draw list
      ByteBuffer dlb = ByteBuffer.allocateDirect(
      // (# of coordinate values * 2 bytes per short)
              drawOrder.length * 2);
      dlb.order(ByteOrder.nativeOrder());
      theDrawListBuffer = dlb.asShortBuffer();
      theDrawListBuffer.put(drawOrder);
      theDrawListBuffer.position(0);
      
      // initialize vertex byte buffer for texture coordinates
      bb = ByteBuffer.allocateDirect(
      // (# of coordinate values * 4 bytes per float)
              theUVCoords.length * 4);
      bb.order(ByteOrder.nativeOrder());
      theUVBuffer = bb.asFloatBuffer();
      theUVBuffer.put(theUVCoords);
      theUVBuffer.position(0);
      
      theProgramHandle = this.createProgram( theVertexShader, theFragmentShader );
      theMVPMatrixHandle = GLES20.glGetUniformLocation(theProgramHandle, "uMVPMatrix");
      
      Log.d("Utils", "Loadtexture");
      Bitmap img = null;
      theTextures = new int[1];
      try {
              img = BitmapFactory.decodeResource(aContext.getResources(), R.drawable.galaxy);
              GLES20.glGenTextures(1, theTextures, 0);
              int tex = theTextures[0];
              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
//              GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
//                    GLES20.GL_REPEAT);
//              GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
//                    GLES20.GL_REPEAT);
              GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, img, 0);
              Log.d("LoadTexture", "Loaded texture"+":H:"+img.getHeight()+":W:"+img.getWidth());
      } catch (Exception e){
              Log.d("LoadTexture", e.toString()+ ":" + e.getMessage()+":"+e.getLocalizedMessage());
      }
      img.recycle();
      

   }
   
   public void draw( float[] aViewMatrix, float[] aProjectionMatrix )
   {
      // Add program to OpenGL environment
      GLES20.glUseProgram(theProgramHandle);
      GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
      GLES20.glEnable(GLES20.GL_BLEND); 
      

      // get handle to vertex shader's vPosition member
      thePositionHandle = GLES20.glGetAttribLocation(theProgramHandle, "vPosition");

      // Enable a handle to the triangle vertices
      GLES20.glEnableVertexAttribArray(thePositionHandle);

      // Prepare the triangle coordinate data
      theVertexBuffer.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
      GLES20.glVertexAttribPointer(thePositionHandle, COORDS_PER_VERTEX,
                                   GLES20.GL_FLOAT, false,
                                   VERTEX_STRIDE, theVertexBuffer);
      checkGlError("glVertexAttribPointer thePosition");
      
      theTextureHandle = GLES20.glGetAttribLocation(theProgramHandle, "a_texCoords");
      checkGlError("glGetAttribLocation atex_Coords");
      if (theTextureHandle == -1) {
          throw new RuntimeException("Could not get attrib location for aTextureCoord");
      }
      GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, theTextures[0]);
      checkGlError("glVertexAttribPointer maTextureHandle");
      theVertexBuffer.position(0);
      GLES20.glEnableVertexAttribArray(theTextureHandle);
      GLES20.glVertexAttribPointer(theTextureHandle, 2, GLES20.GL_FLOAT, false,
            UV_STRIDE, theUVBuffer);
      checkGlError("glEnableVertexAttribArray maTextureHandle");

      float[] mvpMatrix = new float[16];
      
      Matrix.setIdentityM( mvpMatrix, 0 );
      Matrix.multiplyMM( mvpMatrix, 0, aViewMatrix, 0, mvpMatrix, 0 );
      Matrix.multiplyMM( mvpMatrix, 0, aProjectionMatrix, 0, mvpMatrix, 0 );

      // Apply the projection and view transformation
      GLES20.glUniformMatrix4fv(theMVPMatrixHandle, 1, false, mvpMatrix, 0);
      checkGlError("glUniformMatrix4fv");

      // Draw the square
      GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, drawOrder.length,
                            GLES20.GL_UNSIGNED_SHORT, theDrawListBuffer);

      // Disable vertex array
      GLES20.glDisableVertexAttribArray(thePositionHandle);
      GLES20.glDisableVertexAttribArray(theTextureHandle);
      GLES20.glDisable( GLES20.GL_BLEND );
   }

   
/********************* UTILITY METHODS ****************************************   
   
   
   /**
    * Loads and compiles a shader
    * 
    * @param shaderType
    *           integer from GLES20 representing the type of shader
    * @param source
    *           the C code describing the shader
    * @return the handle to the compiled shader
    */
   private int loadShader( int shaderType, String source )
   {
      int shader = GLES20.glCreateShader( shaderType );
      if( shader != 0 )
      {
         GLES20.glShaderSource( shader, source );
         GLES20.glCompileShader( shader );
         int[] compiled = new int[1];
         GLES20.glGetShaderiv( shader, GLES20.GL_COMPILE_STATUS, compiled, 0 );
         if( compiled[0] == 0 )
         {
            Log.e( "GLES20Points", "Could not compile shader " + shaderType
                  + ":" );
            Log.e( "GLES20Points", GLES20.glGetShaderInfoLog( shader ) );
            GLES20.glDeleteShader( shader );
            shader = 0;
         }
      }
      return shader;
   }

   /**
    * Combines vertex and fragment shaders into a compiled program
    * 
    * @param vertexSource
    *           the C code describing the vertex shader
    * @param fragmentSource
    *           the C code describing the fragment shader
    * @return a handle to the compiled program
    */
   private int createProgram( String vertexSource, String fragmentSource )
   {
      int vertexShader = loadShader( GLES20.GL_VERTEX_SHADER, vertexSource );
      if( vertexShader == 0 )
      {
         return 0;
      }

      int pixelShader = loadShader( GLES20.GL_FRAGMENT_SHADER, fragmentSource );
      if( pixelShader == 0 )
      {
         return 0;
      }

      int program = GLES20.glCreateProgram();
      if( program != 0 )
      {
         GLES20.glAttachShader( program, vertexShader );
         checkGlError( "glAttachShader" );
         GLES20.glAttachShader( program, pixelShader );
         checkGlError( "glAttachShader" );
         GLES20.glLinkProgram( program );
         int[] linkStatus = new int[1];
         GLES20.glGetProgramiv( program, GLES20.GL_LINK_STATUS, linkStatus, 0 );
         if( linkStatus[0] != GLES20.GL_TRUE )
         {
            Log.e( "GLES20Points", "Could not link program: " );
            Log.e( "GLES20Points", GLES20.glGetProgramInfoLog( program ) );
            GLES20.glDeleteProgram( program );
            program = 0;
         }
      }
      return program;
   }

   /**
    * Checks GL for errors
    * 
    * @param op
    *           String to send to the log if an error is found.
    */
   private void checkGlError( String op )
   {
      int error;
      while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR )
      {
         Log.e( "GLES20Points", op + ": glError " + error );
         throw new RuntimeException( op + ": glError " + error );
      }
   }
}
